﻿using Microsoft.EntityFrameworkCore;
using SDDP;
using System;
using System.Threading.Tasks;
using DotNetStar.TheKing.Domain;

namespace DotNetStar.TheKing.Services
{
    using DotNetStar.TheKing.Infrastructure;
    using Microsoft.Extensions.Caching.Memory;
    using Models;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;

    /// <summary>
    /// 用户服务
    /// </summary>
    public class UserService : ServiceBase
    {
        IStorage<User, long> _userStorage;
        IStorage<UserPointHistory, Guid> _userPointListStorage;
        IStorage<UserOperationHistory, Guid> _userOperationHistoryStorage;
        IStorage<UserSignIn, Guid> _userSignInStorage;
        IStorage<UserContentPayHistory, Guid> _userContentPayStorage;
        IStorage<Content, long> _contentStorage;
        VerifyCodeService _verifyCodeService;
        PointService _pointService;
        IEventBus _eventBus;

        public UserService(IMemoryCache memoryCache,
            IStorage<User, long> userStorage,
            IStorage<UserPointHistory, Guid> userPointListStorage,
            VerifyCodeService verifyCodeService
            , PointService pointService,
            IStorage<UserOperationHistory, Guid> userOperationHistoryStorage,
            IStorage<UserSignIn, Guid> userSignInStorage,
            IStorage<UserContentPayHistory, Guid> userContentPayStorage,
            IStorage<Content,long> contentStorage,
        IEventBus eventBus
            ) : base(memoryCache)
        {
            this._userStorage = userStorage;
            this._verifyCodeService = verifyCodeService;
            this._userPointListStorage = userPointListStorage;
            this._userOperationHistoryStorage = userOperationHistoryStorage;
            this._userSignInStorage = userSignInStorage;
            this._userContentPayStorage = userContentPayStorage;
            this._pointService = pointService;
            this._contentStorage = contentStorage;

            this._eventBus = eventBus;
        }
        

        /// <summary>
        /// 快速创建没有密码的用户。
        /// </summary>
        /// <param name="mobile"></param>
        /// <param name="userName"></param>
        /// <returns></returns>
        public async Task<User> CreateUserAsync(string mobile, string userName)
        {
            var user = new User
            {
                UserName = userName,
                Mobile = mobile
            };

            user.ConfirmMobile();

            user.GenerateSecurityToken();
            user.Normal();
            user.CalculateMaxExp();
            _userStorage.Add(user);
            await _userStorage.Context.CommitAsync();

            _eventBus.Publish(new UserPointEvent(user.Id, PointStrategyTypes.NewRegister));

            return user;
        }

        public async Task<User> GetUserAsync(string account, string password)
        {
            var user = await GetUserAsync(account); ;
            if (user == null)
            {
                throw new NullReferenceException($"找不到当前帐号");
            }

            if (!PasswordHasher.VelidatePassword(password, user.PasswordSalt, user.Password))
            {
                throw new NullReferenceException("密码不正确");
            }

            user.SetLastLoginTime();
            await _userStorage.Context.CommitAsync();

            return user;
        }

        /// <summary>
        /// 以异步方式获取指定id的用户。
        /// </summary>
        /// <param name="id">用户的id。</param>
        /// <returns></returns>
        public Task<User> GetByIdAsync(long id) => _userStorage.FindAsync(id);
        

        /// <summary>
        /// 以异步方式获取正常访问的用户。
        /// </summary>
        /// <param name="id">The identifier.</param>
        /// <returns></returns>
        /// <exception cref="UserNotNormalException"></exception>
        public async Task<UserDisplayModel> GetNormalUserByIdAsync(long id)
        {
           var user=await  _userStorage.Query(m => m.Status == UserStatus.Normal && m.Id == id).SingleOrDefaultAsync();
            if (user == null)
            {
                throw new UserNotNormalException(id);
            }
            return new UserDisplayModel
            {
                UserId = user.Id,
                AvatarUrl = user.AvatarUrl,
                UserName = user.UserName
            };
        }

        /// <summary>
        /// 以异步方式获取指定唯一字段的用户
        /// </summary>
        /// <param name="userUniqueField">用户的唯一字段标识，如：用户名/手机/邮箱</param>
        /// <returns></returns>
        public Task<User> GetUserAsync(string userUniqueField, out AccountTypes accountType)
        {
            Task<User> userTask = null;
            if (userUniqueField.IsMatch(Extensions.MOBILE_PATTERN))
            {
                userTask =_userStorage. Query(m=>m.Mobile== userUniqueField).SingleOrDefaultAsync();
                accountType = AccountTypes.Mobile;
            }
            else if (userUniqueField.IsMatchEmail())
            {
                userTask = _userStorage.Query(m=>m.Email== userUniqueField).SingleOrDefaultAsync();
                accountType = AccountTypes.Email;
            }
            else
            {
                userTask = _userStorage.Query(m=>m.UserName==userUniqueField).SingleOrDefaultAsync();
                accountType = AccountTypes.UserName;
            }
            return userTask;
        }

        /// <summary>
        /// 以异步方式获取指定唯一字段的用户
        /// </summary>
        /// <param name="userUniqueField">用户的唯一字段标识，如：Id/用户名/手机/邮箱</param>
        /// <returns></returns>
        public Task<User> GetUserAsync(string userUniqueField) 
            => GetUserAsync(userUniqueField, out AccountTypes accountType);

        public Task<User> GetUserAsync(string userUniqueField,AccountTypes accountType)
        {
            switch (accountType)
            {
                case AccountTypes.Email:
                    return _userStorage.Query(m=>m.Email==userUniqueField).SingleOrDefaultAsync();
                case AccountTypes.Mobile:
                    return _userStorage.Query(m => m.Mobile == userUniqueField).SingleOrDefaultAsync();
                case AccountTypes.UserName:
                    return _userStorage.Query(m => m.UserName == userUniqueField).SingleOrDefaultAsync();
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        /// <summary>
        /// 获取指定查询条件表达式的用户集合。
        /// </summary>
        /// <param name="whereExpression">条件表达式。</param>
        /// <returns></returns>
        public Task<IEnumerable<User>> GetUsersAsync(Expression<Func<User,bool>> whereExpression,int top=100)
        => Task.Run(()=>        
             _userStorage.Query(whereExpression).Take(top).ToList().AsEnumerable()
        );

        /// <summary>
        /// 以异步方式重置密码。
        /// </summary>
        /// <param name="user">要重置密码的用户。</param>
        /// <param name="password">要重置的密码。</param>
        /// <returns></returns>
        public async Task<ExecutedResult> ResetPasswordAsync(User user,string password)
        {
            if (user == null)
            {
                throw new ArgumentNullException(nameof(user));
            }

            var passwordSalt = PasswordHasher.GeneratePasswordSalt();
            password = PasswordHasher.GeneratePassword(password, passwordSalt);

            user.SetPassword(password, passwordSalt);
            user.GenerateSecurityToken();

            await _userStorage.Context.CommitAsync();
            _eventBus.Publish(new UserOperationEvent(UserOperations.ResetPwd, user.Id.ToString(), user.Id));
            return ExecutedResult.Success();
        }

        public Task<List<User>> GetTopUserAsync(int top,UserTopOrderBy orderby)
        {
            var query = _userStorage.Query();
            switch (orderby)
            {
                case UserTopOrderBy.LevelDesc:
                   query= query.OrderByDescending(m => m.Level);
                    break;
                case UserTopOrderBy.TradingPointsDesc:
                    query = query.OrderByDescending(m => m.TradingPoints);
                    break;
                case UserTopOrderBy.JoinTimeDesc:
                    query = query.OrderByDescending(m => m.CreatedTime);
                    break;
                default:
                    break;
            }

            return query.Take(top).ToListAsync();
        }

        /// <summary>
        /// 尝试从缓存中获取最后一次用户的积分。用于积分提示。该缓存的有效期是30秒。
        /// </summary>
        /// <param name="userId">要获取的用户Id。</param>
        /// <param name="userPoint">最后一次积分策略项。若返回值为 false，则为 null。</param>
        /// <returns></returns>
        public bool TryGetLastUserPointFromCache(long userId,out UserPointHistory userPoint)
        {
            userPoint = null;
            if(base.MemoryCache.TryGetValue(UserPointHistory.GetUserPointCacheKey(userId),out UserPointHistory point))
            {
                userPoint = point;

                base.MemoryCache.Remove(UserPointHistory.GetUserPointCacheKey(userId));
                return true;
            }
            return false;
        }

        /// <summary>
        /// 获取指定用户的所有积分记录。
        /// </summary>
        /// <param name="userId">The user identifier.</param>
        /// <returns></returns>
        public Task<List<UserPointHistory>> GetUserPointListAsync(long userId)
            => _userPointListStorage.Query(m=>m.UserId==userId).OrderByDescending(m=>m.CreatedTime).ToListAsync();

        public IQueryable<UserOperationHistory> GetUserOperationHistories(long userId)
            => _userOperationHistoryStorage.Query(m => m.UserId == userId);

        /// <summary>
        /// 更新密码，并发送事件
        /// </summary>
        /// <param name="userId">要更新的userId</param>
        /// <param name="password">新密码</param>
        /// <returns></returns>
        public async Task ChangePasswordAsync(long userId,string password,string newPassword)
        {
            var user = await GetByIdAsync(userId);
            if (user == null)
            {
                throw new NullReferenceException("指定的用户不存在");
            }


            if (!PasswordHasher.VelidatePassword(password, user.PasswordSalt, user.Password))
            {
                throw new InvalidOperationException("旧密码不正确");
            }

            var salt = PasswordHasher.GeneratePasswordSalt();
            var pwd= PasswordHasher.GeneratePassword(newPassword, salt);

            user.SetPassword(pwd, salt);

            await UpdateUserAsync(user);

            _eventBus.Publish(new UserOperationEvent(UserOperations.ChangePwd, userId.ToString(), userId));
            
        }

        public async Task UpdateUserAsync(User user)
        {
            user.GenerateSecurityToken();
            await _userStorage.Context.CommitAsync();
        }

        #region 操作记录        
        /// <summary>
        /// 获取指定用户的对指定对象的操作记录。按时间倒叙排列。
        /// </summary>
        /// <param name="userId">The user identifier.</param>
        /// <param name="operationType">Type of the operation.</param>
        /// <param name="objectId">The object identifier.</param>
        /// <returns></returns>
        public IEnumerable<UserOperationHistory> GetUserOperations(long userId, UserOperations? operationType = null, string objectId = default(string))
        {
            var query = _userOperationHistoryStorage.Query(m => m.UserId == userId);

            if (operationType.HasValue)
            {
                query = query.Where(m => m.Type == operationType.Value);
            }

            if (!objectId.IsNullOrWhiteSpace())
            {
                query = query.Where(m => m.ObjectId == objectId);
            }

            return query.OrderByDescending(m => m.CreatedTime).ToList();
        }
        
        #endregion

        /// <summary>
        /// 签到
        /// </summary>
        /// <param name="userId"></param>
        /// <returns></returns>
        public async Task<ExecutedResult<bool>> SignInAsync(long userId)
        {
            if (await IsUserSignInTotayAsync(userId))
            {
                return ExecutedResult<bool>.Failed("今天你已经签到过");
            }
            var signIn = new UserSignIn
            {
                SignDate = DateTime.Now.Date,
                UserId = userId
            };
            _userSignInStorage.Add(signIn);
            await _userSignInStorage.Context.CommitAsync();


            _eventBus.Publish(new UserPointEvent(userId, PointStrategyTypes.SignIn));
            _eventBus.Publish(new UserOperationEvent(UserOperations.SignIn, signIn.Id.ToString(), userId));
            return ExecutedResult<bool>.Success(await IsUserSignInTotayAsync(userId));
        }

        /// <summary>
        /// 判断今天是否签到
        /// </summary>
        /// <param name="userId"></param>
        /// <returns></returns>
        public Task<bool> IsUserSignInTotayAsync(long userId)
            => _userSignInStorage.Query(m => m.SignDate == DateTime.Now.Date && m.UserId == userId).AnyAsync();

        /// <summary>
        /// 获取指定用户的签到记录
        /// </summary>
        /// <param name="userId"></param>
        /// <returns></returns>
        public Task<List<UserSignIn>> GetUserSignInList(long userId, int year, int month)
        {
            var rangeDays= new DateTime(year, month, 1).GetFirstLastThisMounth();

           return _userSignInStorage.Query(m => m.UserId == userId && m.SignDate >= rangeDays.first && m.SignDate <= rangeDays.last)
              .OrderByDescending(m => m.SignDate).ToListAsync();
        }

        /// <summary>
        /// 获取指定用户付费的所有内容。
        /// </summary>
        /// <param name="userId"></param>
        /// <returns></returns>
        public Task<List<UserContentPayHistory>> GetUserContentPayHistories(long userId)
            => _userContentPayStorage.Query(m => m.UserId == userId).OrderByDescending(m => m.CreatedTime).ToListAsync();

        public Task<IEnumerable<User>> GetUserRankAsync<TKey>(int top, Expression<Func<User, TKey>> orderBy)
            => Task.Run(() =>
                (from u in _userStorage.Query()
                 select new User
                 {
                     Id = u.Id,
                     UserName = u.UserName,
                     AvatarUrl = u.AvatarUrl,
                     Birth = u.Birth,
                     Gender = u.Gender,
                     TechExpYear = u.TechExpYear,
                     Signature = u.Signature,
                     Contents = _contentStorage.Query(m => m.AuthorId == u.Id && m.Status== ContentStatus.Normal).ToList()
                 })
            
            .OrderByDescending(orderBy).Take(top)
            .ToList().AsEnumerable()
            );
    }
}
